/*
 * Created on 01.11.2003
 */
package de.javakicker;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;


/** Speichert einen Schnappschuss eines Teams zu einer bestimmten Runde. Dieser enthaelt den
 * Namen des Teams und eine Zuordnungstabelle der Spielernamen auf die Spielerklassen.
 *
 * @author Till Krullmann
 */
public class XmlLineup implements Lineup
{
	
	/**
	 * The Class PlayerClassLoader.
	 */
	private class PlayerClassLoader extends ClassLoader
	{
		
		/** The directory. */
		private File directory;

		/**
		 * Instantiates a new player class loader.
		 *
		 * @param dayId the day id
		 * @param teamName the team name
		 */
		public PlayerClassLoader(int dayId, String teamName)
		{
			directory = new File( "teams" + File.separator + teamName);
		}


		/* 
		 * @see java.lang.ClassLoader#findClass(java.lang.String)
		 */
		public Class<?> findClass(String className)
			throws ClassNotFoundException
		{
			File classFile = new File(directory, className + ".class");

			try
			{
				FileInputStream stream = new FileInputStream(classFile);

				byte[] classData = new byte[ (int) classFile.length() ];
				stream.read(classData);

				return defineClass(className, classData, 0, (int) classFile.length());
			}
			catch (Exception e)
			{
				throw new ClassNotFoundException(
								"Beim Laden der Klasse \"" + className +
								"\" ist ein Fehler aufgetreten.", e);
			}
			catch (Error e)
			{
				throw new ClassNotFoundException(
								"Beim Laden der Klasse \"" + className +
								"\" ist ein schwerer Fehler aufgetreten.", e);
			}
		}
	}


	/**
	 * Speichert einen "Schnappschuss" eines Spielers. Dieser besteht aus einer Zuordnung
	 * Spielername-Spielerklasse.
	 *
	 * @author Till Krullmann
	 */
	public class Player implements Lineup.Player
	{
		
		/** The lineup. */
		private XmlLineup lineup;

		/** The elem. */
		private Element elem;

		/**
		 * Instantiates a new player.
		 *
		 * @param lineup the lineup
		 * @param elem the elem
		 */
		Player(XmlLineup lineup, Element elem)
		{
			this.lineup = lineup;
			this.elem = elem;
		}


		/**
		 * Gets the element.
		 *
		 * @return the element
		 */
		Element getElement()
		{
			return elem;
		}


		/**
		 * Liefert den Namen des Spielers. Diese Eigenschaft stellt eine Referenz auf
		 * den Namen eines {@link Team.Player} dar.
		 *
		 * @return Der Name des Spielers.
		 */
		public synchronized String getName()
		{
			return elem.getAttribute("name");
		}


		/**
		 * �ndert den Namen des Spielers. Diese Eigenschaft stellt eine Referenz auf
		 * den Namen eines {@link Team.Player} dar. Der Name sollte nur direkt nach
		 * der Erzeugung des Objekts gesetzt und dann nicht mehr ge�ndert werden.
		 *
		 * @param name Der neue Name f�r den Spieler.
		 */
		public synchronized void setName(String name)
		{
			elem.setAttribute("name", name);
		}

		/* 
		 * @see javakicker.de.Lineup.Player#getId()
		 */
		public synchronized String getId() {
			return elem.getAttribute("id");
		}
		
		/* 
		 * @see javakicker.de.Lineup.Player#getNo()
		 */
		public synchronized String getNo() {
			return elem.getAttribute("no");
		}
		
		/**
		 * Liefert den Namen der Spielerklasse, ohne die Dateiendung ".class".
		 * Um ein {@link Player}-Objekt der entsprechenden Klasse zu erzeugen,
		 * kann die Methode {@link #newInstance} verwendet werden.
		 *
		 * @return Der Name der Spielerklasse.
		 */
		public synchronized String getClassName()
		{
			return elem.getAttribute("class");
		}


		/**
		 * �ndert den Namen der Spielerklasse. Der Klassenname sollte nur direkt nach
		 * der Erzeugung des Objekts gesetzt und dann nicht mehr ge�ndert werden.
		 *
		 * @param className Der neue Klassenname f�r den Spieler.
		 */
		public synchronized void setClassName(String className)
		{
			elem.setAttribute("class", className);
		}


		/* 
		 * @see javakicker.de.Lineup.Player#getComment()
		 */
		public String getComment()
		{ return null; }


		/**
		 * Erzeugt eine neue Instanz des referenzierten Spielerobjekts, dessen Klassenname
		 * durch {@link #setClassName} festgelegt wurde.
		 *
		 * @return Das neue {@link Player} Objekt.
		 * @throws ClassNotFoundException
		 * 			Wenn die angegebene Klassendatei nicht gefunden wurde oder nicht ge�ffnet
		 * 			werden konnte.
		 * @throws InvalidPlayerException
		 * 			Wenn die angegebene Klassendatei zwar gefunden wurde, aber im falschen
		 * 			Format ist. Dies kann z.B. folgende Ursachen haben:
		 * 			<ul><li>Die Klasse ist keine Unterklasse von {@link Player}.</li>
		 * 				<li>Die Klasse liegt innerhalb eines <code>package</code>.</li>
		 * 				<li>Die Klasse wurde als <code>abstract</code> definiert.</li>
		 * 				<li>Die Klasse besitzt keinen Default-Konstruktor, oder dieser
		 * 					ist nicht <code>public</code>.</li>
		 * 			</ul>
		 * @throws InvocationTargetException
		 * 			Wenn innerhalb des Konstruktors der Spielerklasse eine Exception aufgetreten
		 * 			ist.
		 *
		 */

		public de.javakicker.Player newInstance()
			throws ClassNotFoundException, InvalidPlayerException,
			InvocationTargetException
		{
			ClassLoader playerLoader = new PlayerClassLoader(lineup.getDay(),
				lineup.getTeamName());

			Class<?> playerClass = playerLoader.loadClass(getClassName());

			Constructor<?> playerConstructor = null;

			try
			{
				playerConstructor = playerClass.getConstructor(new Class[]{});
			}
			catch (NoSuchMethodException e)
			{
				throw new InvalidPlayerException(this, "Die Spielerklasse besitzt" +
												 " keinen Default-Konstruktor.", e);
			}

			de.javakicker.Player player = null;
			try
			{
				player = (de.javakicker.Player) playerConstructor.newInstance(new Object[]
					{});
			}
			catch (InstantiationException e)
			{
				throw new InvalidPlayerException(this,
												 "Die Spielerklasse wurde als abstrakt " +
												 "definiert.", e);
			}
			catch (IllegalAccessException e)
			{
				throw new InvalidPlayerException(this,
					"Der Default-Konstruktor der Spielerklasse" +
					"ist nicht public.", e);
			}

			return player;
		}
	}


	/** The Constant DAYTEAMS_DIR. */
	public static final String DAYTEAMS_DIR = "dayteams";

	/** The Constant LINEUP_FILENAME. */
	public static final String LINEUP_FILENAME = "lineup.xml";

	/** The day. */
	private int day;

	/** The doc. */
	private Document doc;

	/** The players. */
	private Collection<Player> players = new LinkedList<Player>();
	
	private String jersey;

	/**
	 * Instantiates a new xml lineup.
	 *
	 * @param doc the doc
	 */
	private XmlLineup(Document doc)
	{
		this.doc = doc;
		loadPlayers();
	}


	/**
	 * Load.
	 *
	 * @param name the name
	 * @return the xml lineup
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws SAXException the sAX exception
	 */
	public static XmlLineup load(String name, boolean path)
		throws IOException, SAXException
	{
		File lineupFile;
		if(path) {
			lineupFile = new File(name + File.separator + LINEUP_FILENAME);
		}
		else {
			String lineupFilename = "teams" + File.separator + name + File.separator + LINEUP_FILENAME;
			lineupFile = new File(lineupFilename);
		}
		
		// Team-Aufstellung fuer diese Runde existiert bereits. Datei oeffnen
		Document doc = XmlFile.readFromFile(lineupFile);

		return new XmlLineup(doc);
	}
	
	/**
	 * Load players.
	 */
	private void loadPlayers()
	{
		jersey = doc.getDocumentElement().getAttribute("jersey");
		for (Node node = doc.getDocumentElement().getFirstChild();
			 node != null && players.size() <= Team.MAXPLAYERS;
			 node = node.getNextSibling())
		{
			if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName() == "player") {
				Player newPlayer = new Player(this, (Element) node);
				players.add(newPlayer);
			}
		}
	}


	/**
	 * Gets the day.
	 *
	 * @return the day
	 */
	public int getDay()
	{
		return day;
	}


	/**
	 * Gets the team name.
	 *
	 * @return the team name
	 */
	public synchronized String getTeamName()
	{
		return doc.getDocumentElement().getAttribute("team");
	}


	/** 
	 * @see javakicker.de.Lineup#getPlayers()
	 */
	public synchronized Lineup.Player[] getPlayers()
	{
		Player[] playersArray = new Player[players.size()];
		return (Player[]) players.toArray(playersArray);
	}


	/** 
	 * @see javakicker.de.Lineup#getPlayer(java.lang.String)
	 */
	public synchronized Lineup.Player getPlayer(String name)
	{
		Iterator<Player> it = players.iterator();
		while (it.hasNext())
		{
			Player player = (Player) it.next();
			if (player.getName() == name)
				return player;
		}

		return null;
	}


	/**
	 * @see javakicker.de.Lineup#addPlayer(java.lang.String)
	 */
	public synchronized Lineup.Player addPlayer(String name)
	{
		Player newPlayer = null;
		if (players.size() < Team.MAXPLAYERS)
		{
			Element newPlayerElem = doc.createElement("player");
			doc.getDocumentElement().appendChild(newPlayerElem);

			newPlayer = new Player(this, newPlayerElem);
			newPlayer.setName(name);

			players.add(newPlayer);
		}
		return newPlayer;
	}


	/**
	 * Adds the player.
	 *
	 * @param name the name
	 * @param className the class name
	 * @return the player
	 */
	public synchronized Player addPlayer(String name, String className)
	{
		Player newPlayer = null;
		if (players.size() < Team.MAXPLAYERS)
		{
			Element newPlayerElem = doc.createElement("player");
			doc.getDocumentElement().appendChild(newPlayerElem);

			newPlayer = new Player(this, newPlayerElem);
			newPlayer.setName(name);
			newPlayer.setClassName(className);

			players.add(newPlayer);
		}
		return newPlayer;
	}


	/**
	 * @see javakicker.de.Lineup#removePlayer(javakicker.de.Lineup.Player)
	 */
	public synchronized void removePlayer(Lineup.Player player)
	{
		if (!(player instanceof Player))
			throw new IllegalArgumentException();

		doc.getDocumentElement().removeChild( ((Player ) player).getElement() );
		players.remove(player);
	}

	@Override
	public String getJersey() {
		return jersey;
	}
}
